package org.llama.jmex.terra;

import java.nio.FloatBuffer;
import java.nio.IntBuffer;

import org.lwjgl.Sys;

import com.jme.math.Vector3f;
import com.jme.util.geom.BufferUtils;


/*
 * Simple implementation of a TerraData
 */
public class SimpleTerraData extends TerraData{
    
    public FloatBuffer verticesbuffer, normalbuffer, texturebuffer;
    public IntBuffer indicesbuffer;
    TerraViewProperties terraProperties;
    
    
    public SimpleTerraData(TerraManager tm, int x, int y, TerraViewProperties terraProperties, Vector3f origin, XYKey xy) {
        this.terraManager = tm;
        this.upperx = x;
        this.uppery = y;
        this.blocksize = tm.getBlockSize();
        this.terraProperties = terraProperties;
        this.origin = origin;
        this.stepScale = terraProperties.terraScale;
        totalSize = blocksize;
        numtex = terraProperties.texturesPerBlock;
        this.xy = xy;
        updateTerra();
    }
    
    public SimpleTerraMesh createTerraMesh() {
        mesh = new SimpleTerraMesh(this);
        return mesh;
    }
    
    
    protected SimpleTerraMesh mesh = null;
    
    //public void setTerraMesh(TerraMesh terramesh) {
    //	this.mesh = terramesh;
    //}
    
    public TerraMesh getTerraMesh() {
        return mesh;
    }
    
    private static IntBuffer bufindices = null;
    
    // private static int[] arrayindices = null;
    
    public void updateMesh() {
        if (mesh == null)
            return;
        
        mesh.setIndexBuffer(0, indicesbuffer);
        
        if (mesh.getVBOInfo(0) != null && terraProperties.useIndicisVBO) {
            mesh.getVBOInfo(0).setVBOIndexID(-1);
        }
        
    }
    
    
    public void updateTerra() {
        long start = Sys.getTime();
        boolean showtext = false;
        buildVertices();
        if (showtext)
            System.out.println("build Vertices: " + (-start + (start = Sys.getTime())));
        buildNormals();
        if (showtext)
            System.out.println("build Normals: " + (-start + (start = Sys.getTime())));
        buildTextureCoordinates();
    }
    
    
    
    private int verticescount = 0;
    
    private void buildVertices() {
        
        int eblock = blocksize + 1;
        verticescount = eblock * eblock;
        verticesbuffer = BufferUtils.createVector3Buffer(verticescount);
        // vertex = new Vector3f[eblock * eblock];
        // System.out.println("hm: "+heightMap.length+" vert: "+vertex.length+"
        // start: "+startx+":"+starty+" bsize: "+blocksize);
        verticesbuffer.rewind();
        for (int x = upperx; x < upperx + eblock; x++) {
            // System.out.print("\nrow..");
            for (int y = uppery; y < uppery + eblock; y++) {
                // System.out.println("pos: "+(x + (y * mapsize))+" posxy:
                // "+x+":"+y+" vert:"+(x - startx+ ((y - starty) * blocksize)));
                
                // unpack the bytes and the uint16 from the int
                // int h = tm.getPoint(x, y);
                // int ex = (byte) ( 0x0ff & (h >> 16) );
                // int ez = (byte) ( 0x0ff & (h >> 24) );
                // h &= 0x0ffff;
                
                // System.out.print("h"+h+"z"+ez+"x"+ex+"|");
                
                // vertex[x - startx+ ((y - starty) * eblock)] =
                // tm.getVector3f(x, y, startx, starty, stepScale);
                
                terraManager.setVector3fInBuffer(verticesbuffer, x - upperx + ((y - uppery) * eblock), x, y, upperx,
                        uppery, stepScale);
                
                // vertex[x - startx+ ((y - starty) * eblock)] = new
                // Vector3f((x-startx+(ex/127f)) * stepScale.x,
                // h * stepScale.y, (y-starty + (ez/127f))
                // * stepScale.z);
            }
        }
        
        // setVertexBuffer(vertBuf);
        // set up the indices
        
        if (bufindices == null) {
            
            int detail = 1;
            int value = ((blocksize / detail) * (blocksize / detail) * 6);
            int[] indices = new int[value];
            
            int count = 0;
            
            // System.out.println("size: " + value);
            // System.out.println("go to: " + (eblock * blocksize) + " with " +
            // detail);
            
            // go through entire array up to the second to last column.
            for (int x = 0; x < blocksize; x += detail) {
                for (int y = 0; y < blocksize; y += detail) {
                    // set the top left corner.
                    indices[count++] = x + (y * eblock);
                    // set the bottom right corner.
                    indices[count++] = x + detail + (eblock * (y + detail));
                    // set the top right corner.
                    indices[count++] = x + detail + (y * eblock);
                    // set the top left corner
                    indices[count++] = x + (y * eblock);
                    // set the bottom left corner
                    indices[count++] = x + (eblock * (y + detail));
                    // set the bottom right corner
                    indices[count++] = x + detail + (eblock * (y + detail));
                }
            }
            
            bufindices = BufferUtils.createIntBuffer(indices);
            
            // bufindices = indexBuffer;
        }
        
        indicesbuffer = bufindices;
        
    }
    
    private final Vector3f _temp3f = new Vector3f();
    
    
    private static FloatBuffer texs;
    
    private void buildTextureCoordinates() {
        
        if (texs == null) {
            
            texs = BufferUtils.createVector2Buffer(verticescount);
            texs.clear();
            
            int eblock = blocksize + 1;
            for (float i = 0; i < eblock; i += 1) {
                for (float k = 0; k < eblock; k += 1) {
                    texs.put((i / blocksize) * numtex);
                    texs.put((k / blocksize) * numtex);
                }
            }
        }
        
        texturebuffer = texs;
    }
    
    private void buildNormals() {
        normalbuffer = BufferUtils.createVector3Buffer(verticescount);
        Vector3f oppositeVector = new Vector3f();
        Vector3f result = new Vector3f();
        
        int normalIndex = 0;
        int eblock = blocksize + 1;
        for (int row = 0; row < eblock; row++) {
            for (int col = 0; col < eblock; col++) {
                
                BufferUtils.setInBuffer(terraManager.getVector3f(upperx + col, uppery + row + 1, upperx, uppery,
                        stepScale, result).subtractLocal(
                        terraManager.getVector3f(upperx + col, uppery + row, upperx, uppery, stepScale, _temp3f))
                        .crossLocal(
                        oppositeVector.set(
                        terraManager.getVector3f(upperx + col + 1, uppery + row, upperx, uppery,
                        stepScale, _temp3f)).subtractLocal(
                        terraManager.getVector3f(upperx + col, uppery + row, upperx, uppery,
                        stepScale, _temp3f))).normalizeLocal(), normalbuffer, normalIndex);
                
                normalIndex++;
            }
        }
        normalbuffer.clear();
        
        // normalbuffer = (BufferUtils.createFloatBuffer(normal));
        // setNormals(normal);
    }
    
    private static final int VERTICES_PER_TRIANGLE = 3;
    private static final float AVERAGE = 0.5f;
    private static Vector3f helperVector = new Vector3f();
    
    private static void interpolateInBuffer(Vector3f vector, float interpolationAmount, FloatBuffer buffer, int index) {
        buffer.put(index * VERTICES_PER_TRIANGLE, (1 - interpolationAmount) * buffer.get(index * VERTICES_PER_TRIANGLE)
        + interpolationAmount * vector.x);
        buffer.put(index * VERTICES_PER_TRIANGLE + 1, (1 - interpolationAmount)
        * buffer.get(index * VERTICES_PER_TRIANGLE + 1) + interpolationAmount * vector.y);
        buffer.put(index * VERTICES_PER_TRIANGLE + 2, (1 - interpolationAmount)
        * buffer.get(index * VERTICES_PER_TRIANGLE + 2) + interpolationAmount * vector.z);
    }
    
    /** Sfera's highly unoptimized method for creating sexy normals
     *  It currently does not take into account the seems between different blocks.
     * */
    private void sferaNormals() {
        normalbuffer = BufferUtils.createVector3Buffer(verticescount);
        indicesbuffer.rewind();
        
        Vector3f faceNormal = new Vector3f();
        int firstIndex, secondIndex, thirdIndex;
        for (int i = 0; i < indicesbuffer.capacity(); i += VERTICES_PER_TRIANGLE) {
            // get the vertex indices for the current face
            firstIndex = indicesbuffer.get();
            secondIndex = indicesbuffer.get();
            thirdIndex = indicesbuffer.get();
            
            // initialize with one of the vertices
            BufferUtils.populateFromBuffer(faceNormal, verticesbuffer, firstIndex);
            
            // the helperVector is now used to calculate the face normal
            helperVector.set(faceNormal);
            
            // subtract the second vertex from the first one
            faceNormal.subtractLocal(verticesbuffer.get(secondIndex * VERTICES_PER_TRIANGLE), verticesbuffer
                    .get(secondIndex * VERTICES_PER_TRIANGLE + 1), verticesbuffer.get(secondIndex
                    * VERTICES_PER_TRIANGLE + 2));
            // subtract the third vertex from the first one
            helperVector.subtractLocal(verticesbuffer.get(thirdIndex * VERTICES_PER_TRIANGLE), verticesbuffer
                    .get(thirdIndex * VERTICES_PER_TRIANGLE + 1), verticesbuffer.get(thirdIndex * VERTICES_PER_TRIANGLE
                    + 2));
            faceNormal.crossLocal(helperVector);
            
            // now the helperVector will be used to retreive the vertex normals
            // and update vertex normals
            interpolateInBuffer(faceNormal, AVERAGE, normalbuffer, firstIndex);
            interpolateInBuffer(faceNormal, AVERAGE, normalbuffer, secondIndex);
            interpolateInBuffer(faceNormal, AVERAGE, normalbuffer, thirdIndex);
        }
        // normalizing is done only afterwards,
        // so bigger triangles (with bigger normals) have a bigger influence
        for (int i = 0; i < verticescount; ++i) {
            BufferUtils.normalizeVector3(normalbuffer, i);
        }
        normalbuffer.rewind();
    }
    
    public void cleanUp() {
        mesh = null;
    }
    
}